package com.news.common;


import com.news.dto.NewsNumber;

import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 协同过滤推荐算法
 */
public class CollaborativeAlgorithm {

    /**
     * 根据所有用户评价列表获取用户推荐列表
     * @param baseUserId
     * @param allList
     */
    public static  List<String> calculate(Integer baseUserId, List<NewsNumber> allList){
        //所有被用户评价+点赞+转发过的新闻ID列表
        Set<Long> newsIdSet = allList.stream().map(e -> e.getNewsId()).collect(Collectors.toSet());
        List<Long> newsIdList=new ArrayList<>();
        newsIdList.addAll(newsIdSet);

        //保存用户"关注的"的新闻ID：userid : newsId+','+newsId
        HashMap<Long,String> userIdMap=new HashMap<>();

        //保存每个新闻"关注"用户数
        HashMap<Long,Long> IdUserNumMap=new HashMap<>();

        //保存baseUserId用户的新闻评价+点赞+转发数量
        HashMap<Long,Long> idNumberMap=new HashMap<>();
        allList.forEach(e->{
            Long newsId = e.getNewsId();
            Long userId = e.getUserId();
            Long number = e.getNumber();
            if(userId==baseUserId.intValue()){
                idNumberMap.put(newsId,number);
            }
            if(userIdMap.containsKey(userId)){
                String newsIdStr= userIdMap.get(userId);
                newsIdStr=newsIdStr+","+newsId;
                userIdMap.put(userId,newsIdStr);
            }else{
                userIdMap.put(userId,newsId+"");
            }
            if(IdUserNumMap.containsKey(newsId)){
                Long userNum=IdUserNumMap.get(newsId);
                userNum++;
                IdUserNumMap.put(newsId,userNum);
            }else{
                IdUserNumMap.put(newsId,1L);
            }
        });

        //计算i j相似度  newsIdI+","+newsIdJ  :  相似度
        HashMap<String,Double> idijMap=new HashMap<>();
        for(int i=0;i<newsIdList.size();i++){
            Long newsIdI = newsIdList.get(i);
            for(int j=i+1;j<newsIdList.size();j++){
                Long newsIdJ = newsIdList.get(j);
                //获取i j同时被喜欢的用户数
                Integer userIdIJNumber = getUserIdIJNumber(newsIdI, newsIdJ, userIdMap);
                //喜欢i的用户数
                Long newsIdINum = IdUserNumMap.get(newsIdI);
                //喜欢j的用户数
                Long newsIdJNum = IdUserNumMap.get(newsIdJ);
                double sqrt = Math.sqrt(newsIdINum * newsIdJNum);
                //ij相似度
                BigDecimal similarityDegree = new BigDecimal(userIdIJNumber).divide(new BigDecimal(sqrt), 3, BigDecimal.ROUND_HALF_UP);
                idijMap.put(newsIdI+","+newsIdJ,similarityDegree.doubleValue());
            }
        }
        //根据用户看到的每个新闻进行相似度推荐
        Set<Long> keySet = idNumberMap.keySet();
        //保存每个推荐新闻newsId与推荐值
        Map<String,Object> recommendScore=new HashMap<>();
        for(Long key:keySet){
            //用户对key的评价+点赞+转发
            Long attentionNum = idNumberMap.get(key);
            //recommendMap里面保存的是用户没有看过的newsId与相似度
            Map<String, Object> recommendMap = getRecommendByNewsId(key, idijMap, idNumberMap);
            recommendMap.keySet().forEach(e->{
                double degree = (double) recommendMap.get(e);
                if(recommendScore.containsKey(e)){
                    double oldVal = (double) recommendScore.get(e);
                    double newVal=oldVal+degree*attentionNum;
                    recommendScore.put(e,newVal);
                }else{
                    double newVal=degree*attentionNum;
                    recommendScore.put(e,newVal);
                }
            });
        }
        //取排名前10返回ID列表
        if(recommendScore.size()<=10){
            List<String> newsIdListRecommend = recommendScore.keySet().parallelStream().collect(Collectors.toList());
            return newsIdListRecommend;
        }else{
            List<String> newsIdListRecommend=new ArrayList<>();
            double tenthDegree = getSexthDegree(recommendScore);
            for(String e :recommendScore.keySet()){
                double degree = (double) recommendScore.get(e);
                if(degree>=tenthDegree){
                    newsIdListRecommend.add(e);
                }
                if(newsIdListRecommend.size()==10){
                    break;
                }
            }
            return newsIdListRecommend;
        }
    }

    //获取最后推荐列表中第10名的推荐值
    private static double getSexthDegree( Map<String,Object> recommendScore){
        ArrayList<Map.Entry<String, Object>> arrayList = new ArrayList<>(recommendScore.entrySet());
        Collections.sort(arrayList, new Comparator<Map.Entry<String, Object>>() {
            @Override
            public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
                return o2.getValue().toString().compareTo(o1.getValue().toString());
            }
        });
        if(arrayList.size()<=10){
            Map.Entry<String, Object> objectEntry = arrayList.get(arrayList.size()-1);
            return Double.parseDouble(objectEntry.getValue().toString());
        }
        Map.Entry<String, Object> objectEntry = arrayList.get(9);
        return Double.parseDouble(objectEntry.getValue().toString());
    }

    /**
     * 返回newsId的推荐列表
     * @param newsId 用户看过的新闻ID
     * @param idijMap 相似度
     * @param idNumberMap 用户看过的新闻与评价+点赞+转发
     * @return
     */
    private static Map<String,Object> getRecommendByNewsId(Long newsId,HashMap<String,Double> idijMap,HashMap<Long,Long> idNumberMap){
        //从idijMap中获取只和newsId有相似度的集合
        HashMap<String,Object> onlyMap=new HashMap<>();
        idijMap.keySet().forEach(e->{
            if(e.contains(newsId.toString())){
                onlyMap.put(e,idijMap.get(e));
            }
        });
        //排序取第十名相似度(如果不够十个返回最后一名相似度)
        double tenthScore = getTenthScore(onlyMap);

        //取10个最高相似度的新闻
        Map<String,Object> tenMap=new HashMap<>();
        for(String e :onlyMap.keySet()){
            double degree= (double) onlyMap.get(e);
            if(degree>=tenthScore){
                tenMap.put(e,degree);
                if(tenMap.size()==10){
                    break;
                }
            }
        }

        //从10个里面获取用户没有评价+点赞+转发过的
        Map<String,Object> recommendMap=new HashMap<>();
        tenMap.keySet().forEach(e->{
            String[] split = e.split(",");
            String currNewsId=null;
            if(split[0].equals(newsId.toString())){
                currNewsId=split[1];
            }else{
                currNewsId=split[0];
            }
            if(!idNumberMap.containsKey(Long.parseLong(currNewsId))){
                recommendMap.put(currNewsId,tenMap.get(e));
            }
        });

        return recommendMap;
    }
    //获取相似度中第十名的分值
    private static double getTenthScore(HashMap<String,Object> onlyMap){
        ArrayList<Map.Entry<String, Object>> arrayList = new ArrayList<>(onlyMap.entrySet());
        Collections.sort(arrayList, new Comparator<Map.Entry<String, Object>>() {
            @Override
            public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
                return o2.getValue().toString().compareTo(o1.getValue().toString());
            }
        });
        if(arrayList.size()<=10){
            Map.Entry<String, Object> objectEntry = arrayList.get(arrayList.size()-1);
            return Double.parseDouble(objectEntry.getValue().toString());
        }
        Map.Entry<String, Object> objectEntry = arrayList.get(9);
        return Double.parseDouble(objectEntry.getValue().toString());
    }

    //获取同时喜欢I J新闻的用户数
    private static Integer getUserIdIJNumber(Long newsIdI,Long newsIdJ,HashMap<Long,String> userIdMap){
        int count=0;
        Set<Long> keySet = userIdMap.keySet();
        for(Long key:keySet){
            String newsIds = userIdMap.get(key);
            if(newsIds.contains(newsIdI+"")&&newsIds.contains(newsIdJ+"")){
                count++;
            }
        }
        return count;
    }
}
